﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace GerbilLib
{
    /// <summary>
    /// List of all supported media types
    /// </summary>
    public enum MediaType { Movie, TvMovie, VideoMovie, Series, MiniSeries, VideoGame, Unkown }

    /// <summary>
    /// Media Item object which holds all meta data information for a movie or tv show.
    /// </summary>
    public class MediaItem : IMediaItem
    {
        /// <summary>
        /// List of meta data fields that can be used in filters, searches etc.
        /// </summary>
        public enum MetaDataFields { Year, ContentRating, ImdbId, TmdbId, TvdbId }
        /// <summary>
        /// Searches for the last occurrence of a year between 1800 and 2099 and removes it 
        /// </summary>
        public static Regex RegexLastYear = new Regex(@"\b(18[0-9]{2}|19[0-9]{2}|20[0-9]{2})\b(?!.*18[0-9]{2}|.*19[0-9]{2}|.*20[0-9]{2})");
        /// <summary>
        /// Searches for the roman numbers after the year in the title string and removes it
        /// </summary>
        public static Regex RegexRomanNumber = new Regex(@"\(.*\/(.*)\)");
        /// <summary>
        /// Default file name for downloaded cover images. Windows Explorer and many other programs
        /// will automatically pick up folder.jpg as the thumbnail image.
        /// </summary>
        public static readonly string DefaultCoverImageFilename = @"\\folder.jpg";
        /// <summary>
        /// Default file name for downloaded backdrop images. Many programs such as Media Browser
        /// and XBMC will automatically pick up backdrop.jpg as the backdrop or fanart image.
        /// </summary>
        public static readonly string DefaultBackdropImageFilename = @"\\backdrop.jpg";

        /// <summary>
        /// Path to the physical location of the media files for this item
        /// </summary>
        private string path = string.Empty;

        #region .   IMediaItem Interface Properties   .
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>
        /// The title.
        /// </value>
        private string title;
        public string Title
        {
            get { return title; } 
            set
            {
                if (title != value)
                {
                    ClearExternalMetaDataIds();
                }
                title = value;
            }
        }
        /// <summary>
        /// Gets or sets the production year.
        /// </summary>
        /// <value>
        /// The production year.
        /// </value>
        public int Year { get; set; }
        /// <summary>
        /// Gets or sets the sort weight.
        /// </summary>
        /// <value>
        /// The sort weight.
        /// </value>
        [XmlIgnore]
        public int SortWeight { get; set; }
        #endregion

        #region .   Public Properties   .
        /// <summary>
        /// Gets or sets the IMDb title rating.
        /// </summary>
        /// <value>
        /// The IMDb title rating.
        /// </value>
        public Single Rating { get; set; }
        /// <summary>
        /// Gets or sets the number of votes on IMDb.
        /// </summary>
        /// <value>
        /// The number of votes.
        /// </value>
        public int Votes { get; set; }
        /// <summary>
        /// Gets or sets the original title. Used when reverting back to original title after matching/editing title.
        /// </summary>
        /// <value>
        /// The original (unmatched) title.
        /// </value>
        public string OriginalTitle { get; set; }
        /// <summary>
        /// Gets or sets the type of the media.
        /// </summary>
        /// <value>
        /// The type of the media.
        /// </value>
        public MediaType MediaType { get; set; }
        /// <summary>
        /// Gets or sets the genre(s).
        /// </summary>
        /// <value>
        /// The genre(s).
        /// </value>
        public string Genre { get; set; }
        /// <summary>
        /// Gets or sets the physical path to the media file location.
        /// </summary>
        /// <value>
        /// The media file path.
        /// </value>
        public string Path
        {
            get { return path; }
            set
            {
                path = value;
                OriginalTitle = System.IO.Path.GetFileName(path);
            }
        }
        /// <summary>
        /// Gets or sets the IMDb ID.
        /// </summary>
        /// <value>
        /// The IMDb ID.
        /// </value>
        public string ImdbId { get; set; }
        /// <summary>
        /// Gets or sets the TVDb ID.
        /// </summary>
        /// <value>
        /// The TVDb ID.
        /// </value>
        public string TmdbId { get; set; }
        /// <summary>
        /// Gets or sets the title description.
        /// </summary>
        /// <value>
        /// The title description.
        /// </value>
        public string TvdbId { get; set; }
        /// <summary>
        /// Gets or sets the TMDb ID.
        /// </summary>
        /// <value>
        /// The TMDb ID.
        /// </value>
        public string Description { get; set; }
        /// <summary>
        /// Gets or sets the IMDb image base URL from which different sizes of the cover image can be determined.
        /// </summary>
        /// <value>
        /// The IMDb image base URL.
        /// </value>
        public string ImageBaseUrl { get; set; }
        /// <summary>
        /// Gets or sets the file extension of the media file.
        /// </summary>
        /// <value>
        /// The file extension of the media file.
        /// </value>
        public string FileExtension { get; set; }
        /// <summary>
        /// Gets or sets the MPAA movie rating.
        /// </summary>
        /// <value>
        /// The MPAA movie rating.
        /// </value>
        public ContentRating ContentRating { get; set; }
        /// <summary>
        /// Gets or sets the runtime in minutes.
        /// </summary>
        /// <value>
        /// The runtime in minutes.
        /// </value>
        public int Runtime { get; set; }
        /// <summary>
        /// Gets or sets a value indicating whether this item has a cover image.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this item has a cover image; otherwise, <c>false</c>.
        /// </value>
        public bool HasCoverImage { get; set; }
        /// <summary>
        /// Gets or set a value for the color in which a media item will be shown in the list.
        /// </summary>
        /// <value>
        /// The media item foreground drawing color.
        /// </value>
        [XmlIgnore]
        public System.Drawing.Color ForeColor { get; set; }
        #endregion

        #region .   Constructors   .
        /// <summary>
        /// Creates a new Media Item object
        /// </summary>
        public MediaItem()
        {
            // Parameterless constructor needed for Xml Serialization
        }

        /// <summary>
        /// Creates a new Media Item object and populates the properties with the given values.
        /// </summary>
        /// <param name="type">Media Type for this item</param>
        /// <param name="year">Production Year</param>
        /// <param name="rating">IMDb Rating</param>
        /// <param name="fileExtension">Media File Extension</param>
        /// <param name="genre">Genres for this item</param>
        /// <param name="title">Title of this item</param>
        /// <param name="path">Path to physical location of media files</param>
        /// <param name="imdbId">IMDb ID</param>
        /// <param name="tmdbId">TMDb ID</param>
        /// <param name="tvdbId">TVDb ID</param>
        /// <param name="imageUrl">URL to IMDb cover image</param>
        /// <param name="votes">Number of votes on IMDb for this item</param>
        /// <param name="description">Description text for this item</param>
        /// <param name="runtime">Runtime in minutes</param>
        public MediaItem(MediaType type = MediaType.Movie, int year = 0, Single rating = 0, string fileExtension = "", string genre = "", string title = "",
            string path = "", string imdbId = "", string tmdbId = "", string tvdbId = "", string imageUrl = "", int votes = 0, string description = "", int runtime = 0)
        {
            MediaType = type;
            Year = year;
            Rating = rating;
            FileExtension = fileExtension;
            Genre = genre;
            Title = title;
            Path = path;
            ImdbId = imdbId;
            TmdbId = tmdbId;
            TvdbId = tvdbId;
            ImageBaseUrl = imageUrl;
            Votes = votes;
            Description = description;
            Runtime = runtime;
        }
        #endregion

        /// <summary>
        /// Gets the cleaned title for the media item. This method is typically
        /// used to clean the year and parentheses from IMDb title matches, so
        /// that you are left only with the actual media title.
        /// If you need to pass in specific title cleaning options, use the
        /// overload that takes a TitleCleaningOptions object as input.
        /// </summary>
        /// <param name="input">The title to be cleaned.</param>
        /// <returns>Cleaned title.</returns>
        public static string GetCleanTitle(string input)
        {
            string[] replaceThese = new string[] { "(", ")", "/" };
            string cleaned = input.ToLower();
            cleaned = RegexRomanNumber.Replace(cleaned, string.Empty);
            cleaned = RegexLastYear.Replace(cleaned, string.Empty);
            foreach (string replace in replaceThese)
                cleaned = cleaned.Replace(replace, string.Empty);

            return cleaned.Trim();
        }

        /// <summary>
        /// Cleans the input string according to the options set in the preferences.
        /// </summary>
        /// <param name="input">The input string.</param>
        /// <param name="titleCleaningOptions">The title cleaning options.</param>
        /// <returns>Cleaned string.</returns>
        public static string GetCleanTitle(string input, TitleCleaningOptions titleCleaningOptions)
        {
            List<string> newNames = new List<string>();

            string cleanedName = input.ToLower();
            foreach (string pattern in titleCleaningOptions.IgnorePatterns) cleanedName = cleanedName.Replace(pattern, "|");
            if (cleanedName.Contains('|')) cleanedName = cleanedName.Substring(0, cleanedName.IndexOf('|'));
            if (titleCleaningOptions.ReplacePeriodsWithSpaces) cleanedName = cleanedName.Replace(".", " ").Trim();

            if (titleCleaningOptions.StripYearFromTitle)
            {
                cleanedName = RegexLastYear.Replace(cleanedName, string.Empty);
                cleanedName = RegexRomanNumber.Replace(cleanedName, string.Empty);
            }

            if (titleCleaningOptions.RemoveBrackets)
            {
                HashSet<string> brackets = new HashSet<string> { "(", ")", "[", "]", "{", "}" };
                foreach (string bracket in brackets)
                    cleanedName = cleanedName.Replace(bracket, string.Empty);
            }

            // Need to restrict string length for correct bitap algorithm execution
            // TODO: Need to not do this for operations that don't use bitap algorithm!
            if (cleanedName.Length > 32) cleanedName = cleanedName.Substring(0, 32);
            return cleanedName.Trim();
        }

        /// <summary>
        /// Cleans the title of this media item according to the options set in the preferences.
        /// </summary>
        /// <param name="titleCleaningOptions">The title cleaning options.</param>
        /// <returns>Cleaned title.</returns>
        public string GetCleanTitle(TitleCleaningOptions titleCleaningOptions)
        {
            return GetCleanTitle(Title, titleCleaningOptions);
        }

        /// <summary>
        /// Gets the IMDb thumbnail image URL.
        /// </summary>
        /// <returns>IMDb thumbnail image URL.</returns>
        public string GetThumbnailImageUrl()
        {
            // TODO: This property should be moved/renamed since there are other cover image sources such as TMDb available too!
            if (string.IsNullOrEmpty(ImageBaseUrl)) return string.Empty;
            return string.Format("{0}._SX99_SY140_.jpg", ImageBaseUrl);
        }

        /// <summary>
        /// Gets the IMDb cover image URL.
        /// </summary>
        /// <returns>IMDb cover image URL.</returns>
        public string GetCoverImageUrl()
        {
            // TODO: This property should be moved/renamed since there are other cover image sources such as TMDb available too!
            if (string.IsNullOrEmpty(ImageBaseUrl)) return string.Empty;
            return string.Format("{0}._SY314_CR4,0,214,314_.jpg", ImageBaseUrl);
        }

        /// <summary>
        /// Gets the IMDb poster image URL.
        /// </summary>
        /// <returns>IMDb poster image URL.</returns>
        public string GetPosterImageUrl()
        {
            // TODO: This property should be moved/renamed since there are other cover image sources such as TMDb available too!
            if (string.IsNullOrEmpty(ImageBaseUrl)) return string.Empty;
            return string.Format("{0}_.jpg", ImageBaseUrl);
        }

        /// <summary>
        /// Determines whether a cover image already exists in the folder.
        /// </summary>
        /// <returns>True if folder.jpg exists and is greater than 0 bytes, otherwise false</returns>
        public bool HasLocalCoverImage()
        {
            string path = string.Format("{0}{1}", Path, DefaultCoverImageFilename);
            if (File.Exists(path))
            {
                FileInfo fi = new FileInfo(path);
                if (fi.Length > 0) return true;            
            }
            return false;
        }

        /// <summary>
        /// Determines whether a media item is missing any meta data.
        /// </summary>
        /// <returns>True if any meta data is missing or empty, otherwise false</returns>
        public bool IsMissingMetaData()
        {
            // TODO: Need to check for TvdbId if TV Show!
            return
                string.IsNullOrWhiteSpace(Description) ||
                string.IsNullOrWhiteSpace(FileExtension) ||
                string.IsNullOrWhiteSpace(Genre) ||
                string.IsNullOrWhiteSpace(ImageBaseUrl) ||
                string.IsNullOrWhiteSpace(ImdbId) ||
                MediaType == GerbilLib.MediaType.Unkown ||
                ContentRating == ContentRating.Unknown ||
                Rating == 0 ||
                Runtime == 0 ||
                Votes == 0 ||
                Year == 0;
        }

        /// <summary>
        /// Reverts the media item title back to the title initially retrieved
        /// when reading the media directory content.
        /// </summary>
        public void RevertTitle()
        {
            // Keep this line in for backwards compatibility with older versions of MediaGerbil that did
            // not have the OriginalTitle property yet.
            if (string.IsNullOrWhiteSpace(OriginalTitle)) OriginalTitle = System.IO.Path.GetFileName(Path);
            Title = OriginalTitle;
        }

        /// <summary>
        /// Clears out all meta data entries in the media item in case it was
        /// accidentally matched with a wrong item. Note that this does not
        /// delete any cover images that might have been downloaded for this item.
        /// </summary>
        public void ClearMetaData()
        {
            RevertTitle();
            Description = Genre = ImageBaseUrl = ImdbId = TmdbId = TvdbId = string.Empty;
            MediaType = GerbilLib.MediaType.Unkown;
            ContentRating = ContentRating.Unknown;
            Rating = Runtime = Votes = Year = 0;
        }

        /// <summary>
        /// Prints out the title string of the MediaItem.
        /// </summary>
        /// <returns>Title string of the MediaItem</returns>
        public override string ToString()
        {
            return Title;
        }

        /// <summary>
        /// Determines whether the media item path is reachable or not. The path might
        /// still exist but be on an unreachable network share.
        /// </summary>
        /// <returns>True if the path is reachable, otherwise false.</returns>
        public bool HasReachablePath()
        {
            return Directory.Exists(Path);
        }

        /// <summary>
        /// Determines whether the current user has access to the location of a
        /// MediaItem's files. This is different from HasReachablePath in that a
        /// path can be reachable but the user might still not have access rights.
        /// This method is mainly used to determine if the user hasn't logged on
        /// to a network share that media items point to.
        /// </summary>
        /// <returns>True if the path can be accessed, otherwise false.</returns>
        public bool CanAccessPath()
        {
            try
            {
                Directory.EnumerateFiles(Path, "*", SearchOption.TopDirectoryOnly);
                ForeColor = System.Drawing.Color.Black;
                return true;
            }
            catch (DirectoryNotFoundException)
            {
                // We can't find the directory, but we can access the path. Since this method is typically
                // used together with HasReachablePath we will return true here.
                return true;
            }
            catch (IOException ex)
            {
                ForeColor = System.Drawing.Color.Gray;
                Logger.LogMessage(string.Format("IOException in MediaItem.CanAccessPath({0}): {1}", Path, ex.Message), LoggingLevel.Debug);
                return false;
            }
            catch (Exception ex)
            {
                ForeColor = System.Drawing.Color.Gray;
                Logger.LogMessage(string.Format("Exception in MediaItem.CanAccessPath({0}): {1}", Path, ex.Message), LoggingLevel.Debug);
                return false;
            }
        }

        /// <summary>
        /// Clears out the internal IDs stored for IMDb and TVDb title association.
        /// </summary>
        private void ClearExternalMetaDataIds()
        {
            ImdbId = TmdbId = TvdbId = string.Empty;
        }
    }

    /// <summary>
    /// The MediaItemEqualityComparer determines whether two MediaItem objects are equal or not.
    /// </summary>
    public class MediaItemEqualityComparer : IEqualityComparer<MediaItem>
    {
        /// <summary>
        /// Determines whether the two MediaItem objects are equal or not.
        /// </summary>
        /// <param name="x">First MediaItem</param>
        /// <param name="y">Second MediaItem</param>
        /// <returns>True if the path of both items is the same, otherwise false</returns>
        public bool Equals(MediaItem x, MediaItem y)
        {
            // All we care about is if they both have the same file path
            // which is case insensitive on Windows systems
            return x.Path.ToLower() == y.Path.ToLower();
        }

        /// <summary>
        /// Serves as the hash function for the MediaItemEqualityComparer type.
        /// </summary>
        /// <param name="obj">MediaItem object</param>
        /// <returns>Hashcode for media item</returns>
        public int GetHashCode(MediaItem obj)
        {
            return obj.GetHashCode();
        }
    }
}
